﻿using System;
using System.Runtime.InteropServices;
using Pfz.Caching;

namespace Pfz.Threading
{
	/// <summary>
	/// Class that speed up load times by creating its value only when requested and
	/// that saves memory by allowing the value to be collected.
	/// Note that value changes may be lost during collections, so you be sure
	/// that the constructor is capable of recreating the most up-to-date object.
	/// </summary>
	public sealed class LazyAndWeak<T>:
		ThreadSafeDisposable,
		IValueLoader<T>
	where
		T: class
	{
		private Func<T> _createDelegate;
		private ThreadUnsafeReference<T> _reference = new ThreadUnsafeReference<T>(null, GCHandleType.Weak);

		/// <summary>
		/// Creates a new LazyAndWeak instance that will create its value using
		/// the default constructor.
		/// </summary>
		public LazyAndWeak()
		{
		}

		/// <summary>
		/// Creates a new LazyAndWeakByDelegate instance setting the delegate used to 
		/// create or recreate the Value property.
		/// </summary>
		public LazyAndWeak(Func<T> createDelegate)
		{
			_createDelegate = createDelegate;
		}

		/// <summary>
		/// Releases the reference, the delegate and possible the value used by this LazyAndWeak object.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				var reference = _reference;
				if (reference != null)
				{
					_reference = null;

					var value = reference.Value;
					reference.Dispose();

					var disposable = value as IDisposable;
					if (disposable != null)
						disposable.Dispose();
				}

				_createDelegate = null;
			}

			base.Dispose(disposing);
		}

		/// <summary>
		/// Gets or creates the value for this LazyAndWeak object.
		/// May return null if it was disposed.
		/// </summary>
		public T Value
		{
			get
			{
				T result;

				lock(DisposeLock)
				{
					if (WasDisposed)
						return null;

					result = _reference.Value;
					if (result == null)
					{
						if (_createDelegate != null)
							result = _createDelegate();
						else
							result = (T)Activator.CreateInstance(typeof(T));

						_reference.Value = result;
					}
				}

				GCUtils.KeepAlive(result);
				return result;
			}
		}

		/// <summary>
		/// Gets the value if it is already in memory.
		/// If not, returns null.
		/// </summary>
		public T InMemoryValue
		{
			get
			{
				lock(DisposeLock)
				{
					if (WasDisposed)
						return null;

					return _reference.Value;
				}
			}
		}
	}
}
